package com.klangner.go.app.widget;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Enumeration;
import java.util.Vector;

import com.klangner.game.board.IBoard;
import com.klangner.go.app.rules.BoardModel;
import com.klangner.go.app.rules.GoConstants;
import com.klangner.go.app.rules.GoGameEvent;
import com.klangner.go.app.rules.GoGameListener;
import com.klangner.go.app.rules.MoveInputDevice;
import com.klangner.go.app.rules.MoveInputDeviceListener;


public class GobanView extends Component implements MoveInputDevice,
                                                    GoGameListener {

  public GobanView(BoardModel position) {
    this.position = position;

    try  {
      jbInit();
    }
    catch (Exception e) {
      e.printStackTrace();
    }

    listeners = new Vector();
    addMouseListener(new MouseAdapter() {
      public void mouseClicked(MouseEvent e) {
        processMouseClicked(e);
      }
    });
  }

  public GobanView() {
    try  {
      jbInit();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }

  /**
   * null out the offscreen buffer as part of invalidation
   */
  public void invalidate() {
      super.invalidate();
      offscreen = null;
  }

  public void paint(Graphics g) {
    if(offscreen == null) {
       offscreen = createImage(getSize().width, getSize().height);
    }

    Graphics og = offscreen.getGraphics();
    og.setClip(0, 0, getSize().width, getSize().height);

    paintEmptyGoban(og);
    displayGoPosition(this.position, og);

    g.drawImage(offscreen, 0, 0, null);
    og.dispose();
  }

  public void update(Graphics g) {
    this.paint(g);
  }

  public Dimension getMinimumSize() {
    int minStoneY = minStone + minStone /5;

    return new Dimension((position.getSize() - 1) * minStone + 2 * dx,
                         (position.getSize() - 1) * minStone + 2 * dy);
  }

  public Dimension getPreferredSize() {
    return getMinimumSize();
  }


  // MoveInputDevice interface method
  public void fireMoveAttempt(int x, int y) {
    Enumeration e = listeners.elements();

    for(;e.hasMoreElements();) {
      ((MoveInputDeviceListener)(e.nextElement())).moveAttempt(x, y);
    }
  }

  // GoGameListener interface method
  public void positionChanged(GoGameEvent goGameEvent) {
    this.position = goGameEvent.getNewPosition();
    this.update(this.getGraphics());
  }

  // GoGameListener interface method
  public void gameOver(GoGameEvent goGameEvent) {}

  public void displayGoPosition(BoardModel position, Graphics g) {
    int size = position.getSize();

    for (int i = 0; i < size; i++)
      for (int k = 0; k < size; k++) {
        displayMove(
                    i, k,
                    position.getPointColor(i, k),
                    g);
      }
  }

  public void displayMove(int x, int y, int state, Graphics g) {
    int px, py; //the physical coordinates of the move to display
    px = xGobanToPhysical(x);
    py = yGobanToPhysical(y);


    if (state == IBoard.BLACK_STONE) {
      g.setColor(Color.black);
      g.fillOval((int)(px - halfcx), (int)(py - halfcy),
                 (int)cx, (int)cy);
    }
    else if (state == IBoard.WHITE_STONE) {
      g.setColor(Color.white);
      g.fillOval((int)(px - halfcx), (int)(py - halfcy),
                 (int)cx, (int)cy);
      g.setColor(Color.black);
      g.drawOval((int)(px - halfcx), (int)(py - halfcy),
                 (int)cx, (int)cy);
    }
  }

  public void setGoPosition(BoardModel newPosition) {
    this.position = newPosition;
    repaint();
  }

  public void addMoveInputDeviceListener(MoveInputDeviceListener listener) {
    listeners.addElement(listener);
  }

  public void removeMoveInputDeviceListener(MoveInputDeviceListener listener) {
    listeners.removeElement(listener);
  }

  public void removeAllMoveInputDeviceListeners() {
    Enumeration e = listeners.elements();
    for (;e.hasMoreElements();) {
      listeners.removeElement((MoveInputDeviceListener)e.nextElement());
    }
  }

  private void paintEmptyGoban(Graphics g) {
    int size = position.getSize();
    gobanWidth = this.getSize().width;
    gobanHeight = this.getSize().height;

    cx = (float)gobanWidth / size;
    cy = (float)gobanHeight / size;
    halfcx = (gobanWidth - ((size - 1) * cx)) / 2;
    halfcy = (gobanHeight - ((size - 1) * cy)) / 2;
    actualRigthMargin = (int)(halfcx + (cx * (size-1)));
    actualBottomMargin = (int)(halfcy + (cy * (size-1)));

    //g.setColor(new Color(208, 189, 134));
    //g.setColor(Color.white);
    g.setColor(Color.lightGray);

    g.fillRect(0, 0, gobanWidth, gobanHeight);

    g.setColor(Color.black);

    for (int i = 0; i < size; i++)
      g.drawLine((int)halfcx, (int)(halfcy + cy * i),
                 actualRigthMargin, (int)(halfcy + cy * i));
    for (int k = 0; k < size; k++)
      g.drawLine((int)(halfcx + cx * k), (int)halfcy,
                 (int)(halfcx + cx * k), actualBottomMargin);
  }

  private void processMouseClicked(MouseEvent e) {
    int x, y;

    x = e.getX();
    y = e.getY();

    fireMoveAttempt(xPhysicalToGoban(x), xPhysicalToGoban(y));
  }

  private int xPhysicalToGoban(int x) {
    return (int)(x / cx);
  }

  private int yPhysicalToGoban(int y) {
    return (int)(y / cy);
  }

  private int xGobanToPhysical(int x) {
    return (int)(halfcx + (x * cx));
  }

  private int yGobanToPhysical(int y) {
    return (int)(halfcy + (y * cy));
  }

  //private int size = 9;
  private int dx = 15, dy = 15;
  private int minStone = 10;

  private BoardModel position;

  private int gobanWidth;
  private int gobanHeight;

  private float cx;
  private float cy;
  private float halfcx;
  private float halfcy;
  private int actualRigthMargin;
  private int actualBottomMargin;

  private Vector listeners;

  private Image offscreen;

  private void jbInit() throws Exception {
    this.addComponentListener(new java.awt.event.ComponentAdapter() {
      public void componentResized(ComponentEvent e) {
        componentResizedHandler();
      }
    });
  }

  private void componentResizedHandler() {
    int oldWidth = getSize().width;
    int oldHeight = getSize().height;

    int newHeigth = (oldHeight >= oldWidth ? oldWidth : oldHeight);
    setSize(new Dimension(newHeigth, newHeigth));
  }
}
